﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace ChartEdit
{
    public enum RenderingMode
    {
        LowQuality,
        HighQuality
    }

    public class Renderer
    {


        #region Variables
        // The editor we are using
        Editor ed;

        // Rendering boolean for threading purposes
        public volatile bool Busy;


        #region editor references
        private int width { get { return ed.Width; } }
        private int height { get { return ed.Height; } }

        /// <summary>
        /// Left X coordinate of the fretboard
        /// </summary>
        private float fbX { get { return ed.FbX; } }
        /// <summary>
        /// Right X coordinate of the fretboard
        /// </summary>
        private float fbXR { get { return ed.FbXR;} }
        /// <summary>
        /// X coordinate of the left side of the fretboard buffered
        /// </summary>
        private float fbXB { get { return ed.FbXB; } }//Left of Fretboard (buffered)
        /// <summary>
        /// Fretboard width
        /// </summary>
        private float fbWidth { get { return ed.FbWidth; } }
        /// <summary>
        /// Buffered fretboard width
        /// </summary>
        private float fbWidthB { get { return ed.FbWidthB; } }

        private OffsetTransformer OT { get { return ed.OT; } }
        private TSManager tSM { get { return ed.tSM; } }
        private int lastEventOffset {get {return ed.lastEventOffset;}} //To make it easy to scroll to the end

        private float SecPerH {get {return ed.SecPerH;}}
        /// <summary>
        /// Amounts of seconds shown from the hitbar to the top of the editor
        /// </summary>
        private float SphScaled {get {return ed.SphScaled;}}//SecPerH*height
        /// <summary>
        /// Place in the chart in seconds
        /// </summary>
        private float COffset {get {return ed.COffset;}} //CrudeOffset - place in chart - 0 is start, 1 is end//time,notoffset 

        private Chart chart {get {return ed.chart;}}
        private NoteTrack noteTrack { get { return ed.noteTrack; } }

        private ThemeManager themeManager { get { return ed.themeManager; } }
        private Theme currentTheme { get { return ed.currentTheme; } }

        #endregion

        private float nOffset;

        /// <summary>
        /// Closest offset to the current position of the song
        /// </summary>
        public int realOffset;//Current offset
        /// <summary>
        /// Offset begin - Doesn't render before this point
        /// </summary>
        public int realOffsetStart;//Previous Beat minus an extra beat
        /// <summary>
        /// Bottom of fretboard/renderer
        /// </summary>
        public float timeOffset;//Current offset in time (seconds)
        /// <summary>
        /// Offset End - Doesn't render beyond this offset
        /// </summary>
        public int realOffsetEnd;//Offset of Beat at the top of the screen
        public float length;

        //Lag compensation
        private System.Diagnostics.Stopwatch sW;
        private float[] executionTimes = new float[32];
        private int eIndex=0;
        //fps
        private ulong count = 0;
        private float[] fps = new float[32];

        // Notes



        // Drawing
        public Bitmap img;
        private Graphics g;


        // Rendering configuration stuff
        public bool DrawGlobalEvents;
        public bool DrawTrackEvents;
        public bool DrawSyncEvents;
        public bool DrawSections;
        public bool DrawNotes;
        public RenderingMode RenderingMode=RenderingMode.HighQuality;


        private Pen[] linePen = new Pen[ 4 ];
        private int[] lineOffsets = new int[ 3 ];
        private Pen redMarker = new Pen( Color.FromArgb( 0x66FF0000 ), 8 );
        private Pen greenMarker = new Pen( Color.FromArgb( 0x6600FF00 ), 8 );
        private Pen greenMarkerThin = new Pen( Color.FromArgb( 0x6600FF00 ), 2 );
        private Font BPMFont = new Font( "Arial", 8 );
        private Font BPMFont2 = new Font( "Arial", 8, FontStyle.Bold );
        private Font eventFont1 = new Font( "Arial", 6 );
        private Font eventFont2 = new Font( "Arial", 6, FontStyle.Bold );
        #endregion

        public float Transform( int offset )
        {
            return height - ( ( OT.GetTime( offset ) - timeOffset ) / SphScaled );
        }

        public float Transform(float time)
        {
            return height - ((time - timeOffset) / SphScaled);
        }
        /// <summary>
        /// Causes the renderer to create a new rendering surface according to its current dimensions.
        /// </summary>
        public void Resize()
        {
            img = new Bitmap((Image)img, Math.Max(width,10), Math.Max(height,10));
            this.g = Graphics.FromImage(img);
            //this.g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
            //this.g.CompositingQuality = CompositingQuality.HighSpeed;
            //this.g.InterpolationMode = InterpolationMode.Low;
            //this.g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
        }
        public void Render()
        {
            Render(RenderingMode);
        }
        /// <summary>
        /// Primary Render Method
        /// </summary>
        public void Render(RenderingMode rMode)
        {
            /////////////////
            Busy = true;
            /////////////////
            lock (ed)
            {
                fps[eIndex] = 1/((float)sW.Elapsed.Ticks / System.TimeSpan.TicksPerSecond);
                sW.Reset();
                sW.Start();
                switch (rMode)
                {
                    case (RenderingMode.HighQuality):
                        g.SmoothingMode = SmoothingMode.AntiAlias;
                        g.CompositingQuality = CompositingQuality.HighQuality;
                        g.InterpolationMode = InterpolationMode.High;
                        break;
                    case (RenderingMode.LowQuality):
                        g.SmoothingMode = SmoothingMode.HighSpeed;
                        g.CompositingQuality = CompositingQuality.HighSpeed;
                        g.InterpolationMode = InterpolationMode.Low;
                        break;
                }
                g.Clear(Color.Black); // Clear the screen

                //Assign some "constants" used for transforming in the drawing methods
                nOffset = COffset + executionTimes.Average()*ed.song.GetSpeed();

                realOffset = OT.GetOffset(nOffset, -SecPerH / 12);//Current offset
                realOffsetStart = OT.GetOffset(nOffset, -SecPerH / 2);
                //timeOffset = OT.GetTime(realOffset);//Current offset time
                timeOffset = nOffset - SecPerH/12;
                realOffsetEnd = OT.GetOffset(nOffset, SecPerH);//Offset of Beat at the top of the screen
                length = (float)(realOffsetEnd - realOffset);


                drawStarpower();
                drawBeatLines();
                //Draw edges of fretboard
                g.DrawLine(linePen[2], fbX, 0f, fbX, (float)height);
                g.DrawLine(linePen[2], fbX + fbWidth, 0f, fbX + fbWidth, (float)height);
                //
                drawHitBar();
                drawSyncEventsLower();
                drawEventsLower();
                drawNoteSustains();
                drawNotes(currentTheme.RealRenderOrder, rMode);
                drawSyncEventsUpper();
                drawTrackEventsUpper();
                drawEventsAndSectionsUpper();
                drawBeginningAndEnd();

                executionTimes[eIndex] = (float)sW.Elapsed.Ticks / TimeSpan.TicksPerSecond;
#if DEBUG
                count++;
                g.DrawString("AvgExecTime: " + (executionTimes.Average() * 1000f).ToString() + "ms", BPMFont, Brushes.Red, 2, 2);
                g.DrawString("FPS: " + fps.Average().ToString(), BPMFont, Brushes.Red, 2, 22);
                g.DrawString(count.ToString() + " iterations", BPMFont, Brushes.Red, 2, 42);
                
#endif
                eIndex++;
                eIndex %= executionTimes.Length;

                //////////////////
                Busy = false;
                //////////////////
            }
        }

        #region Drawing Methods
        
         
        private void drawBeginningAndEnd()
        {
            float Y;
            //Y = height - ((OT.songLength - timeOffset) / SphScaled);
            Y = Transform(chart.LastIndex);
            g.DrawLine(redMarker, fbX, Y, fbXR, Y);
            //Y = height - ((-timeOffset) / SecPerH * height);
            Y = Transform(0);
            g.DrawLine(redMarker, fbX, Y, fbXR, Y);
        }

        private void drawEventsAndSectionsUpper()
        {
            if (DrawGlobalEvents | DrawSections)
            {
                float X;
                float Y;
                for (int i = 0; i < chart.Events.Count; i++)
                {
                    if (chart.Events[i].Type == EventType.Text)
                    {
                        {
                            X = fbXR*1.05f;
                            //Y = height - ((OT.GetTime(chart.Events[i].Offset) - timeOffset) / SecPerH * height);
                            Y = Transform(chart.Events[i].Offset);
                            if (chart.Events[i].TextValue.PadRight(32).Substring(0, 8).ToLower() == "section "
                                & chart.Events[i].TextValue.Length > 8)
                            {
                                if (DrawSections)
                                {
                                    //g.DrawLine(new Pen(Color.FromArgb(unchecked((int)0xAAFFFF22)), 12), fbX1, Y, fbX2, Y);
                                    g.DrawString(chart.Events[i].TextValue.Substring(8), BPMFont2, Brushes.Black, X - 1, Y - 1);
                                    g.DrawString(chart.Events[i].TextValue.Substring(8), BPMFont, Brushes.Yellow, X, Y);
                                }
                            }
                            else
                            {
                                if (DrawGlobalEvents)
                                {
                                    //g.DrawLine(new Pen(Color.FromArgb(unchecked((int)0x777722BB)), 4), fbX1, Y, fbX2, Y);
                                    g.DrawString(chart.Events[i].TextValue, BPMFont2, Brushes.Black, X - 1, Y - 1);
                                    g.DrawString(chart.Events[i].TextValue, BPMFont, Brushes.Magenta, X, Y);
                                }
                            }
                        }
                    }
                }
            }
        }

        private void drawTrackEventsUpper()
        {
            if (DrawTrackEvents & noteTrack != null)
            {
                float X = fbXR * 1.05f;
                float Y;
                int indexStart = OT.nearNIndexEnd(realOffsetStart, noteTrack);
                int indexEnd = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack);
                for (int i = indexEnd; i >= indexStart; i--)
                {
                    if (noteTrack[i].Type != NoteType.Event)
                        continue;
                    Y = Transform(noteTrack[i].Offset);
                    g.DrawString(noteTrack[i].EventName, BPMFont, Brushes.Blue, X, Y);

                }
            }
        }

        private void drawSyncEventsUpper()
        {
            if (DrawSyncEvents)
            {
                for (int i = 0; i < OT.Offset.Count; i++)
                {
                    float Y;
                    //Y = height - ((OT.GetTime(OT.Offset[i]) - timeOffset) / SecPerH * height);
                    Y = Transform(OT.Offset[i]);
                    //g.DrawLine(greenMarker, fbX1, Y, fbX2, Y);
                    g.DrawString(OT.BPM[i].ToString() + " BPM", BPMFont2, Brushes.Black, fbX*0.7f, Y - 1);
                    g.DrawString(OT.BPM[i].ToString() + " BPM", BPMFont, Brushes.White, fbX * 0.7f, Y);
                }
            }
        }
        
        private void drawNotes()
        {
            if (DrawNotes & noteTrack != null)
            {
                float X, Y;
                //DRAW NOATSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
                int indexStart = OT.nearNIndexEnd(realOffsetStart, noteTrack);
                int indexEnd = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack);
                for (int i = indexEnd; i >= indexStart; i--)
                {
                    if (noteTrack[i].Type != NoteType.Regular)
                        continue;
                    Y = Transform(noteTrack[i].Offset);

                    //Get the type of note
                    string Index = "";
                    if (noteTrack[i].IsHopo)
                        Index += "hopo";
                    if (noteTrack[i].IsInSP)
                        Index += "sp";
                    if (noteTrack[i].IsInSlide)
                        Index += "slide";

                    ChartElement chartElem = currentTheme[noteTrack[i].Fret];
                    ChartElementPart C = chartElem[Index];

                    X = fbXB + chartElem.Offset.X * fbWidthB;
                    g.DrawImage(C.ImageResized, X + chartElem.BoundingBoxResized.X, Y + chartElem.BoundingBoxResized.Y);

                }
            }
        } //OBSOLETE//
        
        private void drawNotes(List<int> fretOrder, RenderingMode renderingMode)
        {
            if (DrawNotes & noteTrack != null)
            {
                float yHit = Transform(OT.GetOffset(nOffset));
                for (int j = 0; j < fretOrder.Count; j++)
                {
                    if (fretOrder[j] >= noteTrack.HighestFret)
                        break;
                    if (noteTrack.Count(fretOrder[j]) == 0)
                        continue;
                    float X, Y;
                    //DRAW NOATSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
                    int indexStart = Math.Max(OT.nearNIndexEnd(realOffsetStart, noteTrack, fretOrder[j])-2,0);
                    int indexEnd = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack, fretOrder[j]);

                    float yTop = -currentTheme[j].BoundingBoxResized.Height;
                    for (int i = indexEnd; i >= indexStart; i--)
                    {
                        if (noteTrack[i, fretOrder[j]].Type != NoteType.Regular)
                            continue;
                        Y = Transform(noteTrack[i,fretOrder[j]].Offset);

                        //Break out if we are above the rendering region or below the hit bar
                        if (Y > yHit & noteTrack[i, fretOrder[j]].IsANote)
                            continue;
                        if (Y < yTop)
                            continue;

                        //Get the type of note
                        string Index = "";
                        if (noteTrack[i, fretOrder[j]].IsArmored)
                            Index += "armor";
                        if (noteTrack[i, fretOrder[j]].IsHopo)
                            Index += "hopo";
                        if (noteTrack[i, fretOrder[j]].IsInSlide)
                            Index += "slide";
                        if (noteTrack[i, fretOrder[j]].IsInSP)
                            Index += "sp";

                        ChartElement chartElem = currentTheme[j];
                        ChartElementPart C = chartElem[Index];

                        X = fbXB + chartElem.Offset.X * fbWidthB;
                        switch(renderingMode)
                        {
                            case(RenderingMode.HighQuality):
                                g.DrawImage(C.ImageResized, X + chartElem.BoundingBoxResized.X, Y + chartElem.BoundingBoxResized.Y);
                                break;
                            case(RenderingMode.LowQuality):
                                g.DrawImageUnscaled(C.ImageResized, (int)(X + chartElem.BoundingBoxResized.X), (int)(Y + chartElem.BoundingBoxResized.Y));
                                break;
                        }
                    }
                }
                if (fretOrder.Count - 1 != noteTrack.HighestFret)
                {
                    float X, Y;
                    //DRAW NOATSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
                    int indexStart = OT.nearNIndexEnd(realOffsetStart, noteTrack);
                    int indexEnd = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack);
                    for (int i = indexEnd; i >= indexStart; i--)
                    {
                        if (noteTrack[i].Type != NoteType.Regular)
                            continue;
                        if (fretOrder.Contains(noteTrack[i].Fret))
                            continue;

                        Y = Transform(noteTrack[i].Offset);
                        if (Y > yHit & noteTrack[i].IsANote)
                            continue;
                        if (Y < -currentTheme[noteTrack[i].Fret].BoundingBoxResized.Height)
                            continue;
                        //Get the type of note
                        string Index = "";
                        if (noteTrack[i].IsHopo)
                            Index += "hopo";
                        if (noteTrack[i].IsInSP)
                            Index += "sp";
                        if (noteTrack[i].IsInSlide)
                            Index += "slide";

                        ChartElement chartElem = currentTheme[noteTrack[i].Fret];
                        ChartElementPart C = chartElem[Index];

                        X = fbXB + chartElem.Offset.X * fbWidthB;
                        switch (renderingMode)
                        {
                            case (RenderingMode.HighQuality):
                                g.DrawImage(C.ImageResized, X + chartElem.BoundingBoxResized.X, Y + chartElem.BoundingBoxResized.Y);
                                break;
                            case (RenderingMode.LowQuality):
                                g.DrawImageUnscaled(C.ImageResized, (int)(X + chartElem.BoundingBoxResized.X), (int)(Y + chartElem.BoundingBoxResized.Y));
                                break;
                        }
                    }
                }
            }
        }

        private void drawNoteSustains()
        {
            if (DrawNotes & noteTrack != null)
            {
                float X, Y, Y2, yHit;
                yHit = Transform(OT.GetOffset(nOffset));
                //Subtract the highest known fret from the start index because of the possibility of chords
                int indexStart = Math.Max(OT.nearNIndexEnd(realOffsetStart, noteTrack)-noteTrack.HighestFret,0);
                
                int indexEnd = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack);
                for (int i = indexEnd; i >= indexStart; i--)
                {
                    if (noteTrack[i].Type != NoteType.Regular | noteTrack[i].Length == 0)
                        continue;
                    
                    Y2 = Transform(noteTrack[i].OffsetEnd);
                    if (Y2 > yHit)
                        continue;
                    Y = Math.Min(Transform(noteTrack[i].Offset), yHit);
                    //Get the type of note
                    string Index = "";
                    if (noteTrack[i].IsHopo)
                        Index += "hopo";
                    if (noteTrack[i].IsInSP)
                        Index += "sp";
                    if (noteTrack[i].IsInSlide)
                        Index += "slide";

                    ChartElement chartElem = currentTheme[noteTrack[i].Fret];
                    ChartElementPart C = chartElem[Index];

                    X = fbXB + chartElem.Offset.X * fbWidthB;
                    g.DrawLine(chartElem.SustainPen, X, Y, X, Y2);//sustain
                }
            }
        }

        private void drawEventsLower()
        {
            if (DrawGlobalEvents | DrawSections)
            {
                float Y;
                for (int i = 0; i < chart.Events.Count; i++)
                {
                    switch (chart.Events[i].Type)
                    {
                        case (EventType.Text):
                            {
                                //Y = height - ((OT.GetTime(chart.Events[i].Offset) - timeOffset) / SecPerH * height);
                                Y = Transform(chart.Events[i].Offset);
                                if (chart.Events[i].TextValue.PadRight(32).Substring(0, 8).ToLower() == "section ")
                                {
                                    if (DrawSections)
                                        g.DrawLine(new Pen(Color.FromArgb(unchecked((int)0x77FFCF12)), 10), fbX, Y, fbXR, Y);
                                }
                                else
                                {
                                    if (DrawGlobalEvents)
                                        g.DrawLine(new Pen(Color.FromArgb(unchecked((int)0x777722BB)), 4), fbX, Y, fbXR, Y);
                                }
                                break;
                            }
                    }
                }
            }
        }

        private void drawSyncEventsLower()
        {
            if (DrawSyncEvents)
            {
                for (int i = 0; i < OT.Offset.Count; i++)
                {
                    float Y;
                    //Y = height - ((OT.GetTime(OT.Offset[i]) - timeOffset) / SecPerH * height);
                    Y = Transform(OT.Offset[i]);
                    g.DrawLine(greenMarker, fbX, Y, fbXR, Y);
                    //g.DrawString(OT.BPM[i].ToString() + " BPM", BPMFont2, Brushes.Black, fbX1 - 1, Y - 1);
                    //g.DrawString(OT.BPM[i].ToString() + " BPM", BPMFont, Brushes.White, fbX1, Y);
                }
            }
        }

        private void drawHitBar()
        {

            float Y = Transform(nOffset);
            float X;
            g.DrawLine(redMarker, fbX, Y, fbXR, Y);
            for (int i=0;i<currentTheme.Elements.Count();i++)
            {
                ChartElement c = currentTheme[i];
                X = fbXB + c.Offset.X * fbWidthB;
                RectangleF fRec = new RectangleF(c.BoundingBoxResized.X, c.BoundingBoxResized.Y, c.BoundingBoxResized.Width, c.BoundingBoxResized.Height);
                PointF[] fff = new PointF[1] { new PointF(X, Y) };
                if(c.Offset.X>=0 & c.Offset.X<=1)
                    g.DrawImage(c[0].ImageResized, X + c.BoundingBoxResized.X, Y + c.BoundingBoxResized.Y);


            }
        }

        private void drawBeatLines()
        {
            int step = chart.HalfResolution;
            float Y;
            Pen mrPen;
            for (int i = ((realOffsetStart)-(realOffsetStart%step)); i < realOffsetEnd; i += step)
            {

                switch (tSM[i])
                {
                    case (beatType.Measure):
                        mrPen = linePen[2];
                        break;
                    case (beatType.Beat):
                        mrPen = linePen[3];
                        //continue;
                        break;
                    case (beatType.HalfBeat):
                        mrPen = linePen[1];
                        //continue;
                        break;
                    default:
                        continue;
                    //break;
                }
                //Y = height - ((OT.GetTime(i) - timeOffset) / SecPerH * height);
                Y = Transform(i);
                g.DrawLine(mrPen, fbX, Y, fbXR, Y);
            }
        }

        private void drawStarpower()
        {
            if (DrawNotes & noteTrack != null)
            {
                int start = OT.nearNIndexEnd(realOffsetStart-(16*chart.Resolution), noteTrack);
                int end = OT.nearNIndex(realOffsetEnd + chart.Resolution, noteTrack);

                // DRAW STARRRRRRRRRRRRRRRRRRRRRRPOWER
                float Y2, Y;
                float fbX2 = fbX + fbWidth;
                for (int i = start; i <= end; i++)
                {
                    if (noteTrack[i].Type != NoteType.Special)
                        continue;
                    if (noteTrack[i].SpecialFlag != 2)
                        continue;

                    //Y = height - ((OT.GetTime(noteTrack[i].Offset) - timeOffset) / SecPerH * height);//note center
                    //Y2 = height - ((OT.GetTime(noteTrack[i].Offset + noteTrack[i].Length) - timeOffset) / SecPerH * height); //end of sustain
                    Y = Transform(noteTrack[i].Offset);
                    Y2 = Transform(noteTrack[i].OffsetEnd);
                    RectangleF starBox = new RectangleF(fbX, Y2, fbWidth, Y-Y2);

                    SolidBrush sb = new SolidBrush(Color.FromArgb(128, 0, 0, 128));

                    g.FillRectangle(sb, starBox);
                }
            }
        } 
        #endregion


        public Renderer(Editor ed)
        {
            this.ed = ed;
            this.sW = new System.Diagnostics.Stopwatch();

            #region Measure/Beat Pens
            this.linePen[ 0 ] = new Pen( Brushes.White, 1 );
            this.linePen[ 1 ] = new Pen( Brushes.Gray, 1 );
            this.linePen[ 2 ] = new Pen( Brushes.White, 6 );
            this.linePen[ 3 ] = new Pen( Brushes.White, 3 );
            #endregion

            this.img = new Bitmap( width, height );
            this.g = Graphics.FromImage( img );

            ///this.g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
            //this.g.CompositingQuality = CompositingQuality.HighSpeed;
            //this.g.InterpolationMode = InterpolationMode.NearestNeighbor;
        }

    }
}